package com.wm.blog_admin.interceptor;

/**
 * @author ：半卷流年
 * @description : mybatis拦截器，用来实现对数据脱敏处理，
 * 其实另外一种解决方法是：自定义注解，使用spring aop来实现，本次不采用这种（以前项目使用过），这里使用mybatis提供的来实现
 *
 * @createTime ：2020/6/6
 */

import com.wm.blog_common.annotation.Encryption;
import com.wm.blog_common.constatnt.RegexConstant;
import com.wm.blog_common.util.RegexUitl;
import com.wm.blog_common.util.excel.FieldReflectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;


/**
 * @Intercepts：标识该类是一个拦截器；
 * @Signature：指明自定义拦截器需要拦截哪一个类型，哪一个方法；
 * 2.1 type：对应四种类型中的一种；
 * 2.2 method：对应接口中的哪类方法（因为可能存在重载方法）；
 * 2.3 args：对应哪一个方法；
 */

@Intercepts(
        {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}
)
@Slf4j
@Component //交给spring管理
public class MybatisInterceptor implements Interceptor {


    /**
     * 身份证脱敏正则 保留前后四位
     */
    private final static String IDENTITY_CARD_DESENSITIZATION = "(?<=\\d{4})\\d(?=\\d{4})";
    /**
     *银行卡脱敏正则  保留前后四位
     */
    private final static String BLANK_CARD_DESENSITIZATION = "(?<=\\d{4})\\d(?=\\d{4})";
    /**
     * 手机号脱敏正则 保留前三后四位
     */
    private final static String MOBILE_DESENSITIZATION = "(?<=\\d{3})\\d(?=\\d{4})";

    /**
     * 邮箱脱敏正则 保留前2后2位  1530257155@qq.com
     */
    private final static String EMAIL_DESENSITIZATION = "(?<=(.){2})\\d(?=(.){2})";


    /**
     * QQ脱敏正则 保留前3后3位  15302671
     */
    private final static String QQ_DESENSITIZATION = "(?<=(.){3})\\d(?=(.){3})";


    private static List<String> ignoreSql = Arrays.asList("selectById");


    @Override
    public Object intercept(Invocation invocation) throws Throwable {



        // 获取到返回结果
        Object returnValue = invocation.proceed();
        log.info("拦截的对象：{}",returnValue);

        /**
         * 通过id查询(精准查询的,主要是为了放行查询详情等数据)的sql放行,不进行处理
         */
        Object arg = invocation.getArgs()[0];
        if(arg instanceof MappedStatement){
            MappedStatement mappedStatement = (MappedStatement) arg;
            String id = mappedStatement.getId();
            for (String item : ignoreSql) {
                if(id.contains(item)){
                    return returnValue;
                }
            }
        }
        /**
         * 开始处理标记有注解的数据,仅仅针对返回的list数据进行加密处理，防止查询详情出现问题(精准查询)
         */
        if(!ObjectUtils.isEmpty(returnValue)){
            //数据类型分析
            if (returnValue instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) returnValue;
                if(!CollectionUtils.isEmpty(list)){
                    for (Object target : list) {
                        //获取到所有的字段
                        Field[] fields = FieldReflectionUtil.getAllFields(target.getClass());
                        List<Field> fieldList = Arrays
                                .stream(fields)
                                .filter(item -> item.isAnnotationPresent(Encryption.class))
                                .collect(Collectors.toList());
                        if(!CollectionUtils.isEmpty(fieldList)){
                            for (Field field : fieldList) {
                                field.setAccessible(true);
                                Object o = field.get(target);
                                //针对身份证，邮箱，手机号等做处理
                                String value = this.dealEncryption(o);
                                field.set(target,value);
                            }
                        }
                    }
                }
                log.info("完成脱敏后的数据：{}",returnValue);
            }
        }
        return returnValue;
    }


    /**
     * 处理加密,暂时不需要考虑o为list,map，其他自定义Class等类型的情况 TODO
     * @param o
     */
    private String dealEncryption(Object o) {
        String value = null;
        if(ObjectUtils.isEmpty(o)){
            return value;
        }
        //只处理String类型的
        if(o instanceof String){
            String target = (String) o;
            //是否为身份证，邮箱，银行卡，手机号等数据
            //身份证
            if(RegexUitl.check(target, RegexConstant.ID_CARD_REGEX)){
                value = replaceAction(target,IDENTITY_CARD_DESENSITIZATION);
            }
            //邮箱
            else if(RegexUitl.check(target,RegexConstant.EMIAL_REGEX)){
                value = replaceAction(target,EMAIL_DESENSITIZATION);
            }
            //qq
            else if(RegexUitl.check(target,RegexConstant.QQ_REGEX)){
                value = replaceAction(target,QQ_DESENSITIZATION);
            }
            //手机
            else if(RegexUitl.check(target,RegexConstant.MOBLIE_REGEX)){
                value = replaceAction(target,MOBILE_DESENSITIZATION);
            }
            //银行卡
            else if(RegexUitl.check(target,RegexConstant.BANK_CARD_REGEX)){
                value = replaceAction(target,BLANK_CARD_DESENSITIZATION);
            }else{
                //其他的类型，TODO
                value = dealLessField(target);
            }
        }
        return value;
    }

    private String dealLessField(String value){
        if(StringUtils.isEmpty(value) || value.length() <= 1){
            return value;
        }
        if(value.length() == 2){
            //只展示第一个字
            return value.substring(0,1) + "*";
        }
        //其他情况，只展示首位一个字符
        StringBuilder builder = new StringBuilder();
        builder.append(value.substring(0,1));
        for (int i = 0; i < value.length() - 2; i++) {
            builder.append("*");
        }
        builder.append(value.substring(value.length() - 1));
        return builder.toString();
    }

    /*
     * 脱敏操作 需要脱敏的数据
     * @param source
     * @param regex  正则
     * @return
     */
    private  String replaceAction(String source, String regex) {
        if (StringUtils.isEmpty(source)) {
            return null;
        }
        return source.replaceAll(regex, "*");
    }


    /**
     * 固定写法 原理见 org.apache.ibatis.plugin.Plugin.wrap 方法
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
